
package com.sijiao.difflog.aspect;

import com.sijiao.difflog.annotation.LogAop;
import com.sijiao.difflog.annotation.LogOriginalParam;
import com.sijiao.difflog.annotation.LogResultParam;
import com.sijiao.difflog.constants.ClassTypeEnum;
import com.sijiao.difflog.constants.SourceTypeEnum;
import com.sijiao.difflog.entity.LogResult;
import com.sijiao.difflog.service.IGetOriginalService;
import com.sijiao.difflog.service.IReporterService;
import com.sijiao.difflog.util.BeanUtil;
import com.sijiao.difflog.util.ClassUtil;
import com.sijiao.difflog.util.ContextBeanFactoryUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

@Aspect
@Component
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(LogAspect.class);
    private final ContextBeanFactoryUtil contextBeanFactoryUtil;
    private IReporterService reporterService;

    public LogAspect(ContextBeanFactoryUtil contextBeanFactoryUtil, IReporterService reporterService) {
        this.contextBeanFactoryUtil = contextBeanFactoryUtil;
        this.reporterService = reporterService;
    }

    @Around(value = "@annotation(logAop)", argNames = "proceedingJoinPoint,logAop")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, LogAop logAop) {
        try {
            SourceTypeEnum sourceType = logAop.resultType();
            SourceTypeEnum originalType = logAop.originalType();
            if (sourceType == originalType) {
                if (sourceType != SourceTypeEnum.param) {
                    logger.error("log {} {} source original and result is same one !", proceedingJoinPoint.getClass().getName(), proceedingJoinPoint.getSignature().getName());
                    return process(proceedingJoinPoint);
                }
            }
            Object original = getOriginalInstance(proceedingJoinPoint, originalType, logAop);
            // 结果
            Object result = getResultInstance(proceedingJoinPoint, sourceType, logAop);

            reportLog(logAop.bizName(), original, result);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private Object getOriginalInstance(ProceedingJoinPoint proceedingJoinPoint, SourceTypeEnum originalType, LogAop logAop) {
        if (originalType == SourceTypeEnum.param) {
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            return findParam(proceedingJoinPoint.getArgs(), signature.getMethod().getParameterAnnotations(), LogOriginalParam.class);

        } else if (originalType == SourceTypeEnum.returnValue) {
            return process(proceedingJoinPoint);
        } else if (originalType == SourceTypeEnum.api) {
            IGetOriginalService originalService = getServiceBean(logAop);
            if (originalService != null) {
                MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
                Object originalArgValue = findParam(proceedingJoinPoint.getArgs(), signature.getMethod().getParameterAnnotations(), LogOriginalParam.class);
                String primaryValue = BeanUtil.findPrimaryValue(originalArgValue);
                return originalService.getOriginal(primaryValue);
            }
        }
        return null;
    }

    private Object getResultInstance(ProceedingJoinPoint proceedingJoinPoint, SourceTypeEnum sourceTypeEnum, LogAop logAop) {
        if (sourceTypeEnum == SourceTypeEnum.param) {
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            return findParam(proceedingJoinPoint.getArgs(), signature.getMethod().getParameterAnnotations(), LogResultParam.class);
        } else if (sourceTypeEnum == SourceTypeEnum.returnValue) {
            return process(proceedingJoinPoint);
        } else if (sourceTypeEnum == SourceTypeEnum.api) {
            IGetOriginalService originalService = getServiceBean(logAop);
            if (originalService != null) {
                MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
                Object resultArgValue = findParam(proceedingJoinPoint.getArgs(), signature.getMethod().getParameterAnnotations(), LogResultParam.class);
                String primaryValue = BeanUtil.findPrimaryValue(resultArgValue);
                return originalService.getOriginal(primaryValue);
            }
        }
        return null;
    }

    private Object process(ProceedingJoinPoint proceedingJoinPoint) {
        try {
            return proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            logger.error("{} process {} failed !", proceedingJoinPoint.getClass().getName(), proceedingJoinPoint.getSignature().getName());
            throw new RuntimeException(e);
        }
    }

    private <T extends Annotation> Object findParam(Object[] args, Annotation[][] paramAnnotations, Class<T> annotationClazz) {
        if (args == null) {
            return null;
        }
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (arg == null) {
                continue;
            }
            Annotation[] annotations = paramAnnotations[i];
            for (int j = 0; j < annotations.length; j++) {
                if (annotations[j].annotationType() == annotationClazz) {
                    return arg;
                }
            }
        }
        return null;
    }

    private IGetOriginalService getServiceBean(LogAop logAop) {
        IGetOriginalService getOriginalService = null;
        if (StringUtils.hasText(logAop.beanName())) {
            getOriginalService = contextBeanFactoryUtil.getBean(logAop.beanName(), IGetOriginalService.class);
        }
        if (getOriginalService == null) {
            getOriginalService = contextBeanFactoryUtil.getBean(logAop.beanType());
        }
        return getOriginalService;
    }

    private void reportLog(String bizName, Object original, Object result) {
        Future<LogResult> report = reporterService.report(bizName, original, result);
        try {
            System.out.println(report.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
